/*
* Copyright (c) 2006-2009 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is',  without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose, 
* including commercial applications,  and to alter it and redistribute it
* freely,  subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product,  an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such,  and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.

********************************************************************************

SandBox2D - Tools and Middleware 2011/2012 Assignment 1  - Sam Latif MA002HA

A heavily modified testbed, with added dynamic shape creation/editing, FMOD sounds and XML meta data save/load functionality
 
*/

#include "../Headers/SandBox2D.h"

using namespace std;

void DestructionListener::SayGoodbye(b2Joint* joint)
{
	if (sandBoxApp->m_mouseJoint == joint)
	{
		sandBoxApp->m_mouseJoint = NULL;
	}
	else
	{
		sandBoxApp->JointDestroyed(joint);
	}
}

SandBox2D::SandBox2D()
{
	Settings settings;
	settings.gravity.Set(0.0f,  -10.0f);
	m_world = new b2World(settings.gravity);
	
	m_textLine = 30;
	m_mouseJoint = NULL;
	m_pointCount = 0;
	

	b2BodyDef myBodyDef;
	myBodyDef.type = b2_staticBody;
	myBodyDef.position.Set(0, 0);
	b2Body* staticBody = m_world->CreateBody(&myBodyDef);

	// shape definition for world ground
	b2PolygonShape polygonShape;

	// fixture definition for world ground
	b2FixtureDef myFixtureDef;
	myFixtureDef.shape = &polygonShape;
	
	// initialise and place ground in scene
	polygonShape.SetAsBox( 500, 1, b2Vec2(0, -5), 0);
	staticBody->CreateFixture(&myFixtureDef);
	
	// initialise FMOD
	
	// error checks
	result = FMOD::System_Create(&fmodSystem);		// Create the main system object.
	
	if (result != FMOD_OK)
	{
		printf("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result));
		exit(-1);
	}

	result = fmodSystem->init(100, FMOD_INIT_NORMAL, 0);	// Initialize FMOD.
	if (result != FMOD_OK)
	{
		printf("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result));
		exit(-1);
	}

	// load our sound
	result = fmodSystem->createSound("Sounds\\LgExplode.wav", FMOD_DEFAULT, 0, &sound);	

	m_destructionListener.sandBoxApp = this;
	m_world->SetDestructionListener(&m_destructionListener);
	m_world->SetContactListener(this);
	m_world->SetDebugDraw(&m_debugDraw);
		
	m_stepCount = 0;

	b2BodyDef bodyDef;
	m_groundBody = m_world->CreateBody(&bodyDef);

	memset(&m_maxProfile,  0,  sizeof(b2Profile));
	memset(&m_totalProfile,  0,  sizeof(b2Profile));
}

SandBox2D::~SandBox2D()
{
	// By deleting the world, we delete the shapes,  mouse joint,  etc.
	delete m_world;
	m_world = NULL;
}

void SandBox2D::PreSolve(b2Contact* contact,  const b2Manifold* oldManifold)
{
	const b2Manifold* manifold = contact->GetManifold();

	if (manifold->pointCount == 0)
	{
		return;
	}

	b2Fixture* fixtureA = contact->GetFixtureA();
	b2Fixture* fixtureB = contact->GetFixtureB();

	b2PointState state1[b2_maxManifoldPoints],  state2[b2_maxManifoldPoints];
	b2GetPointStates(state1,  state2,  oldManifold,  manifold);

	b2WorldManifold worldManifold;
	contact->GetWorldManifold(&worldManifold);

	for (int32 i = 0; i < manifold->pointCount && m_pointCount < k_maxContactPoints; ++i)
	{
		ContactPoint* cp = m_points + m_pointCount;
		cp->fixtureA = fixtureA;
		cp->fixtureB = fixtureB;
		cp->position = worldManifold.points[i];
		cp->normal = worldManifold.normal;
		cp->state = state2[i];
		++m_pointCount;
	}
}

void SandBox2D::RenderText(int x,  int y,  const char *string)
{
    m_debugDraw.DrawString(x,  y,  string);
}

class QueryCallback : public b2QueryCallback
{
public:
	QueryCallback(const b2Vec2& point)
	{
		m_point = point;
		m_fixture = NULL;
	}

	bool ReportFixture(b2Fixture* fixture)
	{
		b2Body* body = fixture->GetBody();
		if (body->GetType() == b2_dynamicBody)
		{
			bool inside = fixture->TestPoint(m_point);
			if (inside)
			{
				m_fixture = fixture;

				// We are done,  terminate the query.
				return false;
			}
		}

		// Continue the query.
		return true;
	}

	b2Vec2 m_point;
	b2Fixture* m_fixture;
};

void SandBox2D::MouseDown(const b2Vec2& p)
{
	m_mouseWorld = p;
	
	if (m_mouseJoint != NULL)
	{
		return;
	}

	// Make a small box.
	b2AABB aabb;
	b2Vec2 d;
	d.Set(0.001f,  0.001f);
	aabb.lowerBound = p - d;
	aabb.upperBound = p + d;

	// Query the world for overlapping shapes.
	QueryCallback callback(p);
	m_world->QueryAABB(&callback,  aabb);

	if (callback.m_fixture)
	{
		b2Body* body = callback.m_fixture->GetBody();
		b2MouseJointDef md;
		md.bodyA = m_groundBody;
		md.bodyB = body;
		md.target = p;
		md.maxForce = 100.0f * body->GetMass();
		m_mouseJoint = (b2MouseJoint*)m_world->CreateJoint(&md);
		body->SetAwake(true);
		
		b2Shape *currentShape = callback.m_fixture->GetShape();
		if (currentShape->m_type  == 0)
		{
			shapeType = ballShape;
		}

		else
		{
			shapeType = boxShape;
		}

		SelectedShape = body;

	}

}

void SandBox2D::ShiftMouseDown(const b2Vec2& p)
{
	m_mouseWorld = p;
	
	if (m_mouseJoint != NULL)
	{
		return;
	}
}

void SandBox2D::MouseUp(const b2Vec2& p)
{
	if (m_mouseJoint)
	{
		m_world->DestroyJoint(m_mouseJoint);
		m_mouseJoint = NULL;
	}
}

void SandBox2D::MouseMove(const b2Vec2& p)
{
	m_mouseWorld = p;
	
	if (m_mouseJoint)
	{
		m_mouseJoint->SetTarget(p);
	}
}

void SandBox2D::SetGravity(b2Vec2 gravity)
{
	m_world->SetGravity(gravity);
}


void SandBox2D::SetShape(int bodyType, int fixRotation, b2Vec2 boxSize, float ballSize, float mass, float restitution, float friction)
{
	// loop through all bodies in world
	b2Body* node = m_world->GetBodyList();
	
	while (node)
	{
		b2Body* currentBody = node;
		
		node = node->GetNext();
	
		if (currentBody == SelectedShape)
		{
			
			if (shapeType == boxShape)
			{
				// create new box in place of current one, with same position and velocities
				Box box;
				box.newUserGeneratedBox( bodyType, fixRotation, boxSize, SelectedShape->GetPosition(), SelectedShape->GetAngle(), mass, restitution, friction, SelectedShape->GetLinearVelocity(), SelectedShape->GetAngularVelocity() );
				box.addToWorld(m_world);
				boxes.push_back(box);
				
				// remove from old box from list
				for (unsigned int i = 0; i < boxes.size(); i++)
				{
					if (boxes.at(i).getBody() == SelectedShape)
					{
						boxes.erase(boxes.begin() + i);
					}
				}
				// remove old box from world
				m_world->DestroyBody(currentBody);
			}
			// and repeat if ball
			else if (shapeType == ballShape)
			{
				Ball ball;
				ball.newUserGeneratedBall( bodyType, fixRotation, ballSize, SelectedShape->GetPosition(), SelectedShape->GetAngle(), mass, restitution, friction, SelectedShape->GetLinearVelocity(), SelectedShape->GetAngularVelocity()  );
				ball.addToWorld(m_world);
				balls.push_back(ball);

				for (unsigned int i = 0; i < balls.size(); i++)
				{
					if (balls.at(i).getBody() == SelectedShape)
					{
						balls.erase(balls.begin() + i);
					}
				}

				m_world->DestroyBody(currentBody);
			}
			
 		}
	}
}

void SandBox2D::DeleteShape()
{

	b2Body* node = m_world->GetBodyList();

	while (node)
	{
		b2Body* currentBody = node;

		node = node->GetNext();

		if (currentBody == SelectedShape)
		{

			m_world->DestroyBody(currentBody);

		}
	}
}

void SandBox2D::GenerateBox(int bodyType, int fixedRotation, b2Vec2 size, b2Vec2 position, float mass, float restitution, float friction, int quantity)
{

	for (int i = 0; i < quantity; i++)
	{
		Box box;
		box.newUserGeneratedBox(bodyType, fixedRotation, size, position, 0, mass, restitution, friction, b2Vec2(0, 0), 0);
		box.addToWorld(m_world);
		boxes.push_back(box);
	}
}

void SandBox2D::GenerateBall(int bodyType, int fixedRotation, float radius, b2Vec2 position, float angle, float density, float restitution, float friction, int numOfBalls)
{

	for (int i = 0; i < numOfBalls; i++)
	{
		Ball ball;
		ball.newUserGeneratedBall(bodyType, fixedRotation, radius, position, 0, density, restitution, friction, b2Vec2(0, 0), 0);
		ball.addToWorld(m_world);
		balls.push_back(ball);
	}


}

void SandBox2D::BeginContact(b2Contact* contact)
{
	FMOD::Channel *channel;
	fmodSystem->playSound(FMOD_CHANNEL_FREE, sound, false, &channel);	
}

void SandBox2D::meta(GsgMeta &meta)
{
	meta.elements(boxes);
	meta.elements(balls);
}

void SandBox2D::SaveMeta()
{  

	// take snapshot of box2d simulation values and copy to shape members
	for( std::vector<Box>::iterator iter = boxes.begin(); iter != boxes.end(); iter++ )
	{
		iter->saveShape();
	}

	for( std::vector<Ball>::iterator iter = balls.begin(); iter != balls.end(); iter++ )
	{
		iter->saveShape();
	}

	TiXmlDocument doc("Saves\\SandBoxSave.xml");

	TiXmlElement *root = new TiXmlElement("SandBox2D");
	doc.LinkEndChild( root );
	GsgXMLWriter xml( root );
	this->meta( xml );
	doc.SaveFile();
}

void SandBox2D::LoadMeta()
{

	TiXmlDocument doc;
	doc.LoadFile("Saves\\SandBoxSave.xml");
	TiXmlElement *root = doc.FirstChildElement("SandBox2D");
	GsgXMLReader xml( root );
	this->meta( xml );


	for( std::vector<Box>::iterator iter = boxes.begin(); iter != boxes.end(); iter++ )
	{

		iter->addToWorld( m_world );

	}

	for( std::vector<Ball>::iterator iter = balls.begin(); iter != balls.end(); iter++ )
	{

		iter->addToWorld( m_world );

	}

}

void SandBox2D::Step(Settings* settings)
{
	float32 timeStep = settings->hz > 0.0f ? 1.0f / settings->hz : float32(0.0f);
	
	if (settings->pause)
	{
		if (settings->singleStep)
		{
			settings->singleStep = 0;
		}
		else
		{
			timeStep = 0.0f;
		}
		RenderText( int(settings->screenWidth * 0.5f - 50),  int(settings->screenHeight * 0.5f),  "****PAUSED****" );
		m_textLine += 15;
	}


	uint32 flags = 0;
 	flags += b2Draw::e_shapeBit;
 	flags += b2Draw::e_jointBit;

	m_debugDraw.SetFlags(flags);

	m_world->SetWarmStarting(1);
	m_world->SetContinuousPhysics(1);
	m_world->SetSubStepping(0);

	m_pointCount = 0;
	
	// update fmod
	fmodSystem->update();

	m_world->Step(timeStep,  8,  3);

	m_world->DrawDebugData();

	if (timeStep > 0.0f)
	{
		++m_stepCount;
	}


	if (m_mouseJoint)
	{
		b2Vec2 p1 = m_mouseJoint->GetAnchorB();
		b2Vec2 p2 = m_mouseJoint->GetTarget();

		b2Color c;
		c.Set(0.0f,  1.0f,  0.0f);
		m_debugDraw.DrawPoint(p1,  4.0f,  c);
		m_debugDraw.DrawPoint(p2,  4.0f,  c);

		c.Set(0.8f,  0.8f,  0.8f);
		m_debugDraw.DrawSegment(p1,  p2,  c);
	}
	

}